//+------------------------------------------------------------------+
//|                                                       ZigZag.mq4 |
//|                   Copyright 2006-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2006-2014, MetaQuotes Software Corp."
#property link      "http://www.mql4.com"
#property strict

#property indicator_chart_window
#property indicator_buffers 4
#property indicator_color1  Violet
#property indicator_color2  Red
#property indicator_color3  Blue
#property indicator_color4  White
//---- 指标参数
input int InpDepth=54;     // 高低点是相对与过去多少个Bars
input int InpAlertCount = 3; // 报警次数

int InpDeviation=5;  // 重新计算高低点时，与前一高低点的相对点差
int InpBackstep=3;   // 回退计算的Bars的个数

//---- Buffers
double ExtZigzagBuffer[];  //拐点
double ExtHighBuffer[];    //高点的临时变量数组
double ExtLowBuffer[];     //低点的临时变量数组
double ExtOrderBuffer[];   //开仓点的临时变量数组
//---- 全局变量
int    ExtLevel=3;         //最近已知的三个拐点
int    ExtLastBbIndex = 0;
//+------------------------------------------------------------------+
//| mqh imports                                                      |
//+------------------------------------------------------------------+
#include <Waltz/WaltzBackBone.mqh>
#include <Waltz/WaltzDraw.mqh>
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                             |
//+------------------------------------------------------------------+
int OnInit()
{
	if(InpBackstep>=InpDepth)
	{
		Print("Backstep cannot be greater or equal to Depth");
		return(INIT_FAILED);
	}
	// 1.初始化Buffers
	IndicatorBuffers(4);
	// 2.设置划线的风格
	//SetIndexStyle(0,DRAW_SECTION,0,2);
	//SetIndexStyle(1,DRAW_ARROW,0,2,Red);
	//SetIndexStyle(2,DRAW_ARROW,0,2,Blue);
	SetIndexStyle(3,DRAW_ARROW,0,2,White);
	//SetIndexArrow(1,119);
	//SetIndexArrow(2,119);
	SetIndexArrow(3,119);
	// 3.设置Buffers得索引
	SetIndexBuffer(0,ExtZigzagBuffer);
	SetIndexBuffer(1,ExtHighBuffer);
	SetIndexBuffer(2,ExtLowBuffer);
	SetIndexBuffer(3,ExtOrderBuffer);
	// 4.清空用于显示在图表得Buffer
	SetIndexEmptyValue(0,0.0);
	SetIndexEmptyValue(1,0.0);
   SetIndexEmptyValue(2,0.0);
   SetIndexEmptyValue(3,0.0);
	// 5.设置指标外部调用的参数
	IndicatorShortName("ZigZag("+string(InpDepth)+","+string(InpDeviation)+","+string(InpBackstep)+")");

	return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//|  每个tick跳动一次,仅用于指标,本指标从左往右计算                  |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,			// 价格[] 数组的大小
				const int prev_calculated,		// 前一次调用处理的柱
				const datetime &time[],			// 时间
				const double &open[],			// 开盘价
				const double &high[],			// 最高价
				const double &low[],			// 最低价
				const double &close[],			// 收盘价
				const long& tick_volume[],		// 订单交易量
				const long& volume[],			// 真实交易量
				const int& spread[])			// 点差
{
	int    i,limit,counterZ,whatlookfor=0;
	int    back,pos,lasthighpos=0,lastlowpos=0;
	double extremum;
	double curlow=0.0,curhigh=0.0,lasthigh=0.0,lastlow=0.0;
	// 1.检查历史bar是否足够,和输入参数
	if(rates_total<InpDepth || InpBackstep>=InpDepth)
		return(0);
	// 2.初始化Buffer,和起始位置第InpDepth个Bars
	if(prev_calculated==0)
		limit=InitializeAll();
	else 
	{
		// 找到最近已知的三个拐点（高点或低点）
		i=counterZ=0;
		while(counterZ<ExtLevel && i<100)
		{
			if(ExtZigzagBuffer[i]!=0.0)
				counterZ++;
			i++;
		}
		// 没有发现拐点,从头开始计算
		if(counterZ==0)
			limit=InitializeAll();
		else
		{
			// 设置起始位置来找极值位置
			limit=i-1;
			// 什么样的极值?
			if(ExtLowBuffer[i]!=0.0) 
			{
				// 低点
				curlow=ExtLowBuffer[i];
				// 将寻找下一个高点
				whatlookfor=1;
			}
			else
			{
				// 高点
				curhigh=ExtHighBuffer[i];
				// 将寻找下一个低点
				whatlookfor=-1;
			}
			// 清空第三个拐点后的数值，准备重新计算最后的拐点
			for(i=limit-1; i>=0; i--)  
			{
				ExtZigzagBuffer[i]=0.0;  
				ExtLowBuffer[i]=0.0;
				ExtHighBuffer[i]=0.0;
			}
		}
	}
	// 3.主循环,计算高低点
	for(i=limit; i>=0; i--)
	{
		// 3.1 找到ExtDepth区间内的低点
		extremum=low[iLowest(NULL,0,MODE_LOW,InpDepth,i)];
		// 如果该低点是最后找到的低点
		if(extremum==lastlow)
			extremum=0.0;
		else 
		{ 
			// 标记为的最后找到的低点
			lastlow=extremum; 
			// 是否比上个低点还低InpDeviation*Point
			if(low[i]-extremum>InpDeviation*Point)
				extremum=0.0;
			else
			{
				// 回退InpBackstep个Bar，把比当前低点高的低点纪录值给清空
				for(back=1; back<=InpBackstep; back++)
				{
					pos=i+back;
					if(ExtLowBuffer[pos]!=0 && ExtLowBuffer[pos]>extremum)
						ExtLowBuffer[pos]=0.0; 
				}
			}
		} 
		// 将新的低点进行记录
		if(low[i]==extremum)
			ExtLowBuffer[i]=extremum;
		else
			ExtLowBuffer[i]=0.0;
		
		// 3.2 找到ExtDepth区间内的高点
		extremum=high[iHighest(NULL,0,MODE_HIGH,InpDepth,i)];
		// 如果该高点是最后找到的高点
		if(extremum==lasthigh)
			extremum=0.0;
		else 
		{
			// 标记为的最后找到的高点
			lasthigh=extremum;
			// 是否比上个低点还低InpDeviation*Point
			if(extremum-high[i]>InpDeviation*Point)
				extremum=0.0;
			else
			{
				// 回退InpBackstep个Bar，把比当前高点低的高点纪录值给清空
				for(back=1; back<=InpBackstep; back++)
				{
					pos=i+back;
					if(ExtHighBuffer[pos]!=0 && ExtHighBuffer[pos]<extremum)
						ExtHighBuffer[pos]=0.0; 
				} 
			}
		}
		// 将新的高点进行记录
		if(high[i]==extremum)
			ExtHighBuffer[i]=extremum;
		else
			ExtHighBuffer[i]=0.0;
	}
	
	// 4.最后的处理
	if(whatlookfor==0)
	{
		lastlow=0.0;
		lasthigh=0.0;  
	}
	else
	{
		lastlow=curlow;
		lasthigh=curhigh;
	}
	
	// 5.定义指标的高低点
	for(i=limit; i>=0; i--)
	{
		switch(whatlookfor)
		{
		case 0: 
			// 初期化的情况下，尝试找第一个高点或者是低点
			// lastlow，lasthigh之前已经初始化，再次判断以保证正确性？
			if(lastlow==0.0 && lasthigh==0.0)
			{
				// 下个寻找目标是低点
				if(ExtHighBuffer[i]!=0.0)
				{
					lasthigh=High[i];
					lasthighpos=i;
					whatlookfor=-1;
					ExtZigzagBuffer[i]=lasthigh;
				}
				// 下个寻找目标是高点
				if(ExtLowBuffer[i]!=0.0)
				{
					lastlow=Low[i];
					lastlowpos=i;
					whatlookfor=1;
					ExtZigzagBuffer[i]=lastlow;
				}
			}
			break;  
		// 目前是向上,抹去上个顶底点之间的底，将新发现的底作为最后一个底部后定位新顶
		case 1: 
			if(ExtLowBuffer[i]!=0.0 && ExtLowBuffer[i]<lastlow && ExtHighBuffer[i]==0.0)
			{
				ExtZigzagBuffer[lastlowpos]=0.0;
				lastlowpos=i;
				lastlow=ExtLowBuffer[i];
				ExtZigzagBuffer[i]=lastlow;
			}
			if(ExtHighBuffer[i]!=0.0 && ExtLowBuffer[i]==0.0)
			{
				lasthigh=ExtHighBuffer[i];
				lasthighpos=i;
				ExtZigzagBuffer[i]=lasthigh;
				whatlookfor=-1;
			}   
			break;       
		// 目前是向下,抹去上个顶底点之间的顶，将新发现的顶作为最后一个顶部后定位新底
		case -1: 
			if(ExtHighBuffer[i]!=0.0 && ExtHighBuffer[i]>lasthigh && ExtLowBuffer[i]==0.0)
			{
				ExtZigzagBuffer[lasthighpos]=0.0;
				lasthighpos=i;
				lasthigh=ExtHighBuffer[i];
				ExtZigzagBuffer[i]=lasthigh;
			}
			if(ExtLowBuffer[i]!=0.0 && ExtHighBuffer[i]==0.0)
			{
				lastlow=ExtLowBuffer[i];
				lastlowpos=i;
				ExtZigzagBuffer[i]=lastlow;
				whatlookfor=1;
			}   
			break;               
		}
	}
	//return(rates_total);
	
	// 创建中枢
	ObjectsDeleteAll(0,OBJ_RECTANGLE);
   //ObjectsDeleteAll(0,OBJ_TEXT);
   ObjectsDeleteAll(0,OBJ_ARROW);
   //ObjectsDeleteAll(0,OBJ_TREND);
   StruBackboneList struBbList;
   CreateBackbone(ExtLastBbIndex,struBbList);
   
   // 画中枢
   //DrawBackbone(struBbList);
	
	// 得到当前中枢结束位置,然后遍历ExtZigzagBuffer画出OBV
	StruBackboneInfo curBbInfo;
	for(int index = 0;index < struBbList.count();++index)
	{
   	struBbList.getBbInfo(curBbInfo,index);
   	curBbInfo.dZTop0+=curBbInfo.dShtAvg;
      curBbInfo.dZLow0-=curBbInfo.dShtAvg;
   	int nCurBbStartPos = curBbInfo.nLeftBarID;
   	int nCurBbEndPos = curBbInfo.nRightBarID;
   	if(0 == index)
   	   nCurBbEndPos = 0;
   	for( i = nCurBbEndPos;i<nCurBbStartPos;++i)
   	{
   	   double value = ExtZigzagBuffer[i];
   	   if(value == 0.0)
   	   {
   	      continue;
   	   }
   	   double dObv = iOBV(NULL,0,PRICE_CLOSE,i);
   	   if(dObv == 0.0)
   	      continue;
   	   // 判断顶底
   	   EnumNibType nibType = NibType_None;
   	   if(value>curBbInfo.dZTop0)
   	      nibType = NibType_Top;
   	   else if(value<curBbInfo.dZLow0)
   	      nibType = NibType_Bottom;
   	      
   	   // 和当前中枢前一个高低点比较,找背离
   	   color nColor = Red;
   	   for(int b = i+1;b<nCurBbStartPos;++b)
   	   {
   	      double value2 = ExtZigzagBuffer[b];
   	      if(value2 == 0.0)
   	      {
   	         continue;
   	      }
   	      double dObv2 = iOBV(NULL,0,PRICE_CLOSE,b);
   	      if(dObv2 == 0.0)
   	         continue;
   	      // 判断前一个顶底
      	   EnumNibType nibType2 = NibType_None;
      	   if(value2>curBbInfo.dZTop0)
      	      nibType2 = NibType_Top;
      	   else if(value2<curBbInfo.dZLow0)
      	      nibType2 = NibType_Bottom;
      	   // 都是顶部
      	   if(nibType == NibType_Top && nibType2 == NibType_Top)
      	   {
      	      if(value2 <= value && dObv2>=dObv)
      	      {
      	         nColor = White;
      	      }
      	      else if(value2 >= value && dObv2<=dObv)
      	      {
      	         nColor = LightPink;
      	      }
      	   }
      	   // 都是底部
   	      else if(nibType == NibType_Bottom && nibType2 == NibType_Bottom)
      	   {
               if((value2 >= value && dObv2<=dObv))
      	      {
      	         nColor = White;
      	      }
      	      else if(value2 <= value && dObv2>=dObv)
      	      {
      	         nColor = LightPink;
      	      }
      	   }
   	   }
   	   //DrawText(DoubleToStr(dObv,0),i,value,nColor);
   	   if(Red != nColor && (curBbInfo.nLowNum+curBbInfo.nLowNum)>3)
   	      ExtOrderBuffer[i] = value;
   	}
	}
	static int nAlertCnt = InpAlertCount;
	// 报警
	if(ExtOrderBuffer[1] != 0.0)
	{
	   if(nAlertCnt>0)
	   {
	      string strMsg = "提示：" + Symbol() + " 注意 "+IntegerToString(Period())+" 分钟;";
   	   Alert(strMsg);
   	   Print(strMsg);
   	   --nAlertCnt;
	   }
	}
	else
	{
	   if(nAlertCnt<=0)
	   {
	      nAlertCnt = InpAlertCount;
	   }
	}
	//--- done
	return(rates_total);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int InitializeAll()
{
	ArrayInitialize(ExtZigzagBuffer,0.0);
	ArrayInitialize(ExtHighBuffer,0.0);
	ArrayInitialize(ExtLowBuffer,0.0);
	// 第一次计算位置
	return(Bars-InpDepth);
}
//+------------------------------------------------------------------+


// 从右到左计算指定数量的中枢
void CreateBackbone(int& nExtLastBbIndex,StruBackboneList &bbList)                     
{
   StruBackboneInfo bbInfoTemp;
   int    nBbIndex      =  0;       
   bool   bFirstNib     =  true;
   double dZTop1Temp    =  0.0;
   double dZLow1Temp    =  0.0;
   double dNewTopTemp   =  0.0;
   double dNewLowTemp   =  0.0;
   double dPreValue     =  0.0;
   
   /*/
   if (Bars < 5000)
      return;
   for(int i=5000;i>=0;--i)
   /*/
   for(int i=0;i<Bars;++i)
   {
      double nValue = ExtZigzagBuffer[i];
      if(0.0==nValue)
         continue;
      
      // 从左到右忽略第一个，因为未完成无法判断顶底造成误判
      if(bFirstNib)
      {
         dPreValue = nValue;
         bFirstNib = false;
         continue;
      }
      
      // 正式开始计算的第一个bar
      if(bbInfoTemp.nRightBarID == 0) 
         bbInfoTemp.nRightBarID = i;
      
      // 找底
      if(dPreValue > nValue)
      {
         // 初始化
         if(bbInfoTemp.eStartNibType == NibType_None)
            bbInfoTemp.eStartNibType = NibType_Top;
         if(0.0==bbInfoTemp.dZLow0)
         {
            bbInfoTemp.dZLow0 = nValue;
            bbInfoTemp.dZLow1 = nValue;
         }
         if(NibType_None == bbInfoTemp.eStartNibType)
            bbInfoTemp.eStartNibType = NibType_Bottom;
         
         // 记录
         dNewLowTemp = nValue;
         dPreValue = nValue;
         bbInfoTemp.eEndNibType = NibType_Bottom;
         bbInfoTemp.dSumLowPower += nValue; 
         ++bbInfoTemp.nLowNum;
      }
      else if(dPreValue < nValue)// 找顶
      {
         // 初始化
         if(bbInfoTemp.eStartNibType == NibType_None)
            bbInfoTemp.eStartNibType = NibType_Bottom;
         if(0.0==bbInfoTemp.dZTop0)
         {
            bbInfoTemp.dZTop0 = nValue;
            bbInfoTemp.dZTop1 = nValue;
         }
         if(NibType_None == bbInfoTemp.eStartNibType)
            bbInfoTemp.eStartNibType = NibType_Top;
         
         // 记录
         dNewTopTemp = nValue;
         dPreValue = nValue;
         bbInfoTemp.eEndNibType = NibType_Top;
         bbInfoTemp.dSumTopPower += nValue; 
         ++bbInfoTemp.nTopNum;
      }
      else
      {
         //Print("BackBone Err: PreValue == nValue!");
         continue;
      }
      
      if(0.0 == dNewTopTemp || 0.0 == dNewLowTemp)
      {
         continue;
      }
      // 计算方向和偏离值
      bbInfoTemp.calcReverse();
      // 开始计算中枢
      // ▙▜ 或 ▛▟
      bool bBackbone = (dNewLowTemp >= bbInfoTemp.dZLow0-bbInfoTemp.dShtAvg && dNewLowTemp <= bbInfoTemp.dZTop0+bbInfoTemp.dShtAvg) 
                    || (dNewLowTemp <= bbInfoTemp.dZLow0-bbInfoTemp.dShtAvg && dNewTopTemp >= bbInfoTemp.dZLow0-bbInfoTemp.dShtAvg);
      if(bBackbone)
      {
         bbInfoTemp.dZLow0 = MathMax(bbInfoTemp.dZLow0,dNewLowTemp);
         bbInfoTemp.dZTop0 = MathMin(bbInfoTemp.dZTop0,dNewTopTemp);
         bbInfoTemp.nLeftBarID = i;

         // 先保存前一个最高低点,以免出现新的中枢后,记录的是最新的顶底
         dZTop1Temp=bbInfoTemp.dZTop1;
         dZLow1Temp=bbInfoTemp.dZLow1;
         
         if(bbInfoTemp.dZLow1 > dNewLowTemp)
            bbInfoTemp.dZLow1 = dNewLowTemp;
         if(bbInfoTemp.dZTop1 < dNewTopTemp)
            bbInfoTemp.dZTop1 = dNewTopTemp;
      }
      // 已经是新的中枢
      else
      {
         // 减去多计算的
         -- bbInfoTemp.nTopNum;
         -- bbInfoTemp.nLowNum;
         bbInfoTemp.dSumTopPower -= dNewTopTemp;
         bbInfoTemp.dSumLowPower -= dNewLowTemp;
         
         // 重置极值点
         if(bbInfoTemp.eEndNibType == NibType_Top)     // 从右往左,确认一个底部离开中枢,更新最高点
            bbInfoTemp.dZLow1 = dZLow1Temp;
         if(bbInfoTemp.eEndNibType == NibType_Bottom)  // 从右往左,确认一个顶部离开中枢,更新最低点
            bbInfoTemp.dZTop1 = dZTop1Temp;

         // 计算偏移量和中枢反向
         bbInfoTemp.calcReverse();
         
         // 计算支撑阻挡
         bbInfoTemp.calcMedianValue();
         
         // 保存该中枢信息
         bbList.addItem(bbInfoTemp);

         // 该中枢结束
         bbInfoTemp.eStartNibType = bbInfoTemp.eEndNibType;
         bbInfoTemp.eEndNibType = NibType_None;
         bbInfoTemp.nRightBarID = bbInfoTemp.nLeftBarID;
         bbInfoTemp.dZTop0 = dNewTopTemp;
         bbInfoTemp.dZLow0 = dNewLowTemp;
         bbInfoTemp.dZTop1 = bbInfoTemp.dZTop0;
         bbInfoTemp.dZLow1 = bbInfoTemp.dZLow0;
      
         // 多空力度
         bbInfoTemp.dSumTopPower = bbInfoTemp.dZTop0;
         bbInfoTemp.dSumLowPower = bbInfoTemp.dZLow0;

         bbInfoTemp.nTopNum=1;
         bbInfoTemp.nLowNum=1;

         
         // 只处理指定数量的中枢
         //if(++nBbIndex >= BackboneList_Size)
         //   break;
      }
   }
}


/*/
Zigzag指标算法
如官方所述：Zigzag指标是连接一系列价格点的趋势线。所以Zigzag主要用途是来标识过去价格中的相对高低点，并以这些点之间的连线来表示这段价格变动的趋势。

1.Zigzag的3个参数

Zigzag在识别高低点的过程中，主要设置了以下三个参数：ExtDepth, DextDeviation
以及ExtBackstep。程序中的表示：
extern int ExtDepth=12;
extern int ExtDeviation=5;
extern int ExtBackstep=3;
说明：
ExtDepth：用于设置高低点是相对与过去多少个Bars(价格图形中的一个柱子)而言。Mt4中默认是12。
ExtDeviation：用于设置重新计算高低点时，与前一高低点的相对点差。默认值是5， 也就是说如果
A)当前高点>上个高点 5 ,或者
B)当前低点<上个低点 –
5的情况下，则会对之前计算过的ExtBacksteps个Bars值的高低点进行重新计算。
ExtBackstep：用于设置回退计算的Bars的个数。

2.Zigzag算法

1对计算位置进行初期化
1.1判断是否是第一次进行高低点计算，如果是，则设定计算位置为除去ExtDepth个图形最初的部分。
1.2如果之前已经计算过，找到最近已知的三个拐点（高点或低点），将计算位置设置为倒数第三个拐点之后，重新计算最后的拐点。
2.从步骤1已经设置好的计算位置开始，将对用于存储高低点的变量进行初始化，准备计算高低点
2.1计算ExtDepth区间内的低点，如果该低点是当前低点，则进行2.1.1的计算，并将其记录成一个低点。
2.1.1如果当前低点比上一个低点值小于相对点差(ExtDeviation)；并且之前ExtBackstep个Bars的记录的中，高于当前低点的值清空。
2.2高点的计算如同2.1以及分支处理2.1.1。
3.从步骤1已经设置好的计算位置开始，定义指标高点和低点
3.1如果开始位置为高点，则接下来寻找低点，在找到低点之后，将下一个寻找目标定义为高点
3.2如果开始位置为低点，则与3.1反之。

3.举个例子说明：

假设上次计算的结果如下：倒数第14个Bar出现了一个高点(3.1)，倒数第4个是低点(1.5)，
倒数第1个是新的高点(2.1)——因为距离倒数第14已经大于ExtDepth(14-1>12)。

Bar-14　　　Bar-4　　Bar-1 　　Bar-Current
高(3.1)　　低(1.5)　　高(2.1) 　　 X

对于Bar-Current，即当前的价格X，
CaseI.
如果 X >=2.1
ExtDeviation，则根据Zigzag的定义，这将是一个新的高点。假设这里X=2.3，那么我们绘制指标的时候应该成为：

Bar-14 　　　Bar-4　　Bar-Current
高(3.1) 
　　低(1.5)　　高(2.3)    

  CaseII.
  如果 1.5 - ExtDeviation<
  X<2.1 ExtDeviation，则我们继续等待价格的变化，所绘制的指标也不会变化。

  CaseIII.
  如果 1.5 - ExtDeviation>=
  X，则这是一个新的低点。假设这里X=1.3，则我们绘制指标的时候应该成为：
  Bar-14　　　Bar-Current
  高(3.1)  　　低(1.3)

  这个时候，之前的Bar-4因为在我们定义的ExtBackstep之内(1-4)，所以他的最低值会被清空，
  根据算法第三步的定义，我们会一直寻找低点直到发现Bar-Current，这时候已经遍历过Bar-1，所以Bar-1定义的高点也不再成为拐点。
  这也就是所谓的重绘部分，也因此诟病为“未来函数”——因为所看见的当前最后的高低点可能在下个时间段里面被抹去。
/*/